package game;


import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Random;

/**
 * This class represents the game board
 * @author group19
 * White(W) goes up and - integer and 0 belongs to it as a bar, 27 it's winner bar
 * Black(B) goes down and + integer 25 belongs to it as a bar, 26 it's winner bar
 * 1-24 board presentation
 * 
 */
public class BGBoard implements Cloneable {

    private final byte WHITE_BAR = 0,  WHITE_OFF = 27,
                       BLACK_BAR = 25, BLACK_OFF = 26;
    /*
     *
     */
    protected final byte[] startingBoard = {0, 
                                                  -2, 0, 0, 0, 0, 5, //1-6
                                                   0, 3, 0, 0, 0,-5, //7-12
                                                   5, 0, 0, 0,-3, 0, //13-18
                                                  -5, 0, 0, 0, 0, 2, //19-24
                                                   0, 0, 0};

    public enum Player {

        ANN, PUBEVAL
    };

    // used for AI
	boolean knocked;
	byte[] applyedMove;
	float score;
    
	// for Human and AI
    public static enum have {

        BLACK, WHITE, NONE
    };
    protected String player_white = "ANN", player_black = "PUBEVAL";
    protected byte[] board;
    protected byte[] dice;
    protected ArrayList<byte[]> moves;
    public static final boolean WHITE = true; 	// statics used in code for readability
    protected static final boolean BLACK = false; // statics used in code for readability
    public boolean whosMove; // true = White, false = Black
    protected boolean readyWhite = false;
    protected boolean readyBlack = false;
    protected Random random = new Random();
    protected boolean winner; // if there is a winner in the game

    /*
     * Initialization
     */
    public BGBoard() {
        //board = new byte[]{0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, -14};
        board = startingBoard;
    	checkHomeReady(); // for testing purpose
    }
    
    /*
     * Copy of the state
     */
    public BGBoard(BGBoard temp){
    }

    public void initializeBoard() {

        /*
         * To start the game, each player throws a single die. This determines both the player
         * to go first and the numbers to be played. If equal numbers come up,
         * then both players roll again until they roll different numbers.
         * The player throwing the higher number now moves his checkers according
         * to the numbers showing on both dice. After the first roll, the players
         * throw two dice and alternate turns.
         */
        while (true) {
            byte[] startDice = diceRoll();
            //System.out.println("Prerolls: White [" + startDice[0] + "]" + " Black [" + startDice[1] + "]");
            whosMove = (startDice[0] > startDice[1]) ? WHITE : BLACK;
            if (startDice[0] != startDice[1]) {
                //System.out.println(getWhoMoveVerbose() + " begins round");
                break;
            }
        }
    }

    /**
     * Check if the input move is legal
     * @param byte moveTo
     * @param byte whereFrom
     * @return boolean
     */
    public boolean isLegalMove(byte whereFrom, byte moveTo) {
    	//System.out.println("isLegalMove(" + whreFrom + )
        if (whereFrom == moveTo) {
            return false;
        }
        
        // fix moveTo
        moveTo = (moveTo > 24) ? 25 : moveTo;
        moveTo = (moveTo < 1) ? 0 : moveTo;

        if (readyBlack && !whosMove) { // can be optimised to (readyBlack && !whosMove) -> readability sucks!
            if (moveTo == 0) {
                return true;
            }
        } else if (readyWhite && whosMove) {
            if (moveTo == 25) {
                return true;
            }
        }
        
        if (moveTo == 25 || moveTo == 0) // if it is not ready for return then not supposed to!
            return false;

        
        if (whosMove == BLACK)  				// Black to move   
        	return (board[moveTo] >= -1);   //true if target position has maximum 1 white piece
        else 									//White to move
        	return (board[moveTo] <= 1); 		//true if target position has maximum 1 black piece

    }
    
    /**
     * Check if the input move is legal
     * @param byte moveTo
     * @param byte whereFrom
     * @return boolean
     */
    public boolean isLegalMoveCopy(byte whereFrom, byte moveTo) {
        if (whereFrom == moveTo) {
            return false;
        }
        
        // fix moveTo
        moveTo = (moveTo > 24) ? 25 : moveTo;
        moveTo = (moveTo < 1) ? 0 : moveTo;

        if (readyBlack && !whosMove) { // can be optimised to (readyBlack && !whosMove) -> readability sucks!
            if (moveTo == 0) {
                return true;
            }
        } else if (readyWhite && whosMove) {
            if (moveTo == 25) {
                return true;
            }
        }
        if (moveTo == 25 || moveTo == 0) // if it is not ready for return then not supposed to!
        {
            return false;
        }

        byte many = (byte) Math.abs(board[moveTo]);

        // position have what
        if (board[moveTo] == 0) {
            return true;
        }
        if (board[moveTo] > 0) { // Player.BLACK
            if (whosMove == BLACK) {
                return true;
            } else if (many == 1) {
                return true;
            }
        } else if (board[moveTo] < 0) { // Player.<
            if (whosMove == WHITE) {
                return true;
            } else if (many == 1) {
                return true;
            }
        }
        return false;
    }

    /**
     * Take the piece and put it to the correct position
     * @param byte moveTo
     * @param byte moveTo
     * @return boolean
     */
    public void applyMove(byte from, byte to) {
    	//System.out.println("applyMove("+from +","+to+")");
    	knocked = false; // renew the knocked or not
    	applyedMove = new byte[]{from,to};
        if (whosMove == WHITE) {
            board[from] = (byte) (board[from] + 1);
            putPeice(to);
        } else {
            board[from] = (byte) (board[from] - 1);
            putPeice(to);
        }

        //"removes" a used dice by setting its value to zero
        boolean findDice = true;
        byte moveDistance = (byte) Math.abs(from - to);
    	int i = 0;
        while(findDice){
        	if (dice[i] == moveDistance){
        		dice[i] = 0;
        		findDice = false;
        	}
        	i++;
        }
        checkHomeReady(); // state check
    }

    /*
     * Put piece in the desired position
     */
    private void putPeice(byte moveTo) {
        if (moveTo <= 0) {
            moveTo = 26;
        } else if (moveTo > 24) {
            moveTo = 27;
        }
        if (whosMove == BLACK && board[moveTo] >= 0) // Player.BLACK
        {
            board[moveTo] = (byte) (board[moveTo] + 1);
        } else if (whosMove == WHITE && board[moveTo] <= 0) // Player.WHITE
        {
            board[moveTo] = (byte) (board[moveTo] - 1);
        } else { // kicking each other's piece
        	knocked = true;
            if (whosMove == BLACK) {
                board[moveTo] = 1;// as taking away the Player.WHITE piece
                board[0] = (byte) (board[0] - 1);
            } else {
                board[moveTo] = -1;// as taking away the Player.WHITE piece
                board[25] = (byte) (board[25] + 1);
            }
        }
    }

    /*
     * Board check for the ready for home
     */
    protected void checkHomeReady() {
        byte pieceW = 0;
        byte pieceB = 0;
        for (int i = 1; i < 7; i++) { // for black and white
            pieceW += board[25 - i];
            pieceB += board[i];
        }
        
        pieceW += board[27];
        pieceB += board[26];
        if (pieceW == -15) {
            readyWhite = true;
        }
        if (pieceB == 15) {
            readyBlack = true;
        }
    }

    /**
     * Dicethrow
     * @return 
     */
    public byte[] diceRoll() {
    	/*
        byte diceA = Byte.valueOf(String.valueOf(random.nextInt(6) + 1));
        byte diceB = Byte.valueOf(String.valueOf(random.nextInt(6) + 1));
        byte sum = Byte.valueOf(String.valueOf(diceA + diceB));
        dice = new byte[]{diceA, diceB, sum};
        return new byte[]{diceA, diceB, sum};*/
    	
        byte diceA = Byte.valueOf(String.valueOf(random.nextInt(6) + 1));
        byte diceB = Byte.valueOf(String.valueOf(random.nextInt(6) + 1));
        
        if(diceA == diceB){
            dice = new byte[]{diceA,diceA,diceA,diceA};
        	return dice;
        }else{
        	dice = new byte[]{diceA,diceB};
        	return dice;
        }
        //dice = new byte[]{5,6};
        //return dice;
    }

    /*
     * Update board for the home opening
     * can be used for the !! need to connect with the commands or logic class
     */
    public void updateBoard() {
    }

    /**
     * This method returns whos move it is
     * @return True = WHITE, False = BLACK
     */
    public boolean getWhoMove() {
        return whosMove;
    }

    /**
     * String version of whosMove ^^
     * @return
     */
    public String getWhoMoveVerbose() {
        return (getWhoMove()) ? "White" : "Black";
    }

    /*
     * This method returns dice
     */
    /**
     * This method returns how many pieces that are at a specific position
     * @param position
     * @return
     */
    public int nrPieces(int position) {
        return Math.abs(board[position]);
    }

    /**
     * This method returns what color of the pieces on a specific position
     *
     * @param position
     * @return Returns True if Player.WHITE, returns False if Player.BLACK Note: in the case of zero
     * pieces it also returns True. So this needs to be checked also.
     */
    public boolean piececolor(int position) {
        if (board[position] < 0) {
            return false;
        }
        return true;
    }

    public boolean isEmpty(int position) {
        return board[position] == 0;
    }
    
    /**
     * Race condition occur when the player pieces are on opposite sides. That is, all 
     * the pieces have passed each other and no player can knock out the other.
     * @return True if is is a race, false if there is contact
     */
    public boolean isRace(){
    	//if someone has on bar, its contact
        if (board[BLACK_BAR] != 0 || board[WHITE_BAR] != 0) {
            return false;
        }
        
        int i = 1;
        int TotalBlacks = board[26]; //add pieces off board to total
        while(board[i] >= 0 && i<=24){ //step through board until we find a negative piece
        	TotalBlacks += board[i];
        	i++;
        }    
        //position 'i' has a negative piece so all blacks should have been counted to 15 if its a race
        return (TotalBlacks == 15);
    }

    @Override
    public String toString() {

        final String newline = System.getProperty("line.separator");
        final char POS = 'B', NEG = 'W', EMPTY = '\'', WALL = '|';
        final String BAR = "\t" + WALL + "                  " + WALL
                + "BAR" + WALL + "                  " + WALL;

        String[] lines = new String[15];
        int currentLine;

        /*
        for (int i : board)
        System.out.print(i + " ");
        System.out.println();
         */

        currentLine = 0;

        lines[currentLine] = "\n\t+13-14-15-16-17-18------19-20-21-22-23-24-+";

        currentLine++;

        for (int i = 1; i <= 5; i++) {

            lines[currentLine] = "\t" + WALL;

            for (int j = 13; j <= 24; j++) {
                if (Math.abs(board[j]) < i) {
                    lines[currentLine] += " " + EMPTY + " ";
                } else {
                    if (i == 5 && Math.abs(board[j]) > 5) {
                        if (Math.abs(board[j]) > 9) {
                            lines[currentLine] += Math.abs(board[j]) + " ";
                        } else {
                            lines[currentLine] += " " + Math.abs(board[j]) + " ";
                        }
                    } else {
                        if (board[j] < 0) {
                            lines[currentLine] += " " + NEG + " ";
                        } else {
                            lines[currentLine] += " " + POS + " ";
                        }
                    }
                }
                if (j == 18) {
                    if (i == 4 && board[BLACK_BAR] > 0) {
                        lines[currentLine] += WALL + "" +board[BLACK_BAR] + " " + POS + WALL;
                    } else {
                        lines[currentLine] += WALL + "   " + WALL;
                    }
                }
            }

            lines[currentLine] += WALL;

            currentLine++;
        }

        lines[currentLine++] = "";
        lines[currentLine++] = BAR;
        lines[currentLine++] = "";


        for (int i = 5; i >= 1; i--) {

            lines[currentLine] = "\t" + WALL;

            for (int j = 12; j >= 1; j--) {
                if (Math.abs(board[j]) < i) {
                    lines[currentLine] += " " + EMPTY + " ";
                } else {
                    if (i == 5 && Math.abs(board[j]) > 5) {
                        if (Math.abs(board[j]) > 9) {
                            lines[currentLine] += Math.abs(board[j]) + " ";
                        } else {
                            lines[currentLine] += " " + Math.abs(board[j]) + " ";
                        }
                    } else {
                        if (board[j] < 0) {
                            lines[currentLine] += " " + NEG + " ";
                        } else {
                            lines[currentLine] += " " + POS + " ";
                        }
                    }
                }

                if (j == 7) {
                    if (i == 4 && board[WHITE_BAR] < 0) {
                        lines[currentLine] += WALL + "" + Math.abs(board[WHITE_BAR]) + " " + NEG + WALL;
                    } else {
                        lines[currentLine] += WALL + "   " + WALL;
                    }
                }
            }

            lines[currentLine] += WALL;

            currentLine++;
        }

        lines[currentLine] = "\t+12-11-10--9--8--7-------6--5--4--3--2--1-+";

        lines[1] += "  " + player_black;
        lines[10] += "  " + player_white;
        if (board[BLACK_OFF] > 0) {
            lines[2] += "  " + POS + ": " + board[BLACK_OFF];
        }
        if (board[WHITE_OFF] < 0) {
            lines[11] += "  " + NEG + ": " + Math.abs(board[WHITE_OFF]);
        }

        String out = new String();

        for (String s : lines) {
            out += s + newline;
        }

        out = newline + out + newline + newline;

        return out;
    }
    /* for testing purpose
    public static void main(String [] args) {
    BGBoard b = new BGBoard();
    b.initializeBoard();
    b.whosMoveItIs = Player.WHITE;
    b.applyMove((byte)1, (byte)2);
    b.whosMoveItIs = Player.BLACK;
    b.applyMove((byte)6, (byte)2);

    System.out.println(b.board[1]);
    System.out.println(b.board[2]);
    System.out.println(b);
    b.start();
    }*/

    /*
     * Get possible positions
     * white goes left and up
     * Black goes right and down
     */
    public void GetPossiblePositions() {
        moves = new ArrayList<byte[]>();
        byte mrMax;
        
        if (whosMove == WHITE) { //White to move! Here we go...
        	
        	if(board[0] < 0) //If White has pieces on the bar, then don't do big loop
        		mrMax = 0;
        	else
        		mrMax = 24;
        		
        	for (byte i = 0; i <= mrMax; i++) {
                if (board[i] < 0) { 					// has a white piece :)
                	for(int d = 0; d <dice.length; d++){ //go through all dice, letting dice = 0 slip for now.
                		if (isLegalMove(i, (byte) (i + dice[d])) && isNotDuplicate(i, (byte) (i + dice[d]))) {
                			moves.add(new byte[]{i, (byte) (i + dice[d])});
                		}
                	}
                }
        	}
        } else {
        	if(board[25] > 0) //If Black has pieces on the bar, then don't do big loop
        		mrMax = 25;
        	else
        		mrMax = 1;
        	
        	for (byte i = 25; i >= mrMax ; i--) {        	
                if (board[i] > 0) { //has a black piece :)
                	for(int d = 0; d <dice.length; d++){ //go through all dice, letting dice = 0 slip for now.
                		if (isLegalMove(i, (byte) (i - dice[d])) && isNotDuplicate(i, (byte) (i - dice[d]))) {
                			moves.add(new byte[]{i, (byte) (i - dice[d])});
                    
                		}
                	}
                }
        	}
        }
        
        
    }
    /**
     * ensures we get no duplicates in the move list to save time later
     */
    private boolean isNotDuplicate(byte from, byte to){
    	for(int i = 0; i < (moves.size()); i++){
    		if (moves.get(i)[0] == from && moves.get(i)[1] == to){
    			return false;
    		}
    	}
    	return true;
    }
    
    public void GetPossiblePositionsCopy() {
        //moves = new ArrayList<byte[]>();
        for (int i = 0; i < 25; i++) {

            if (whosMove == WHITE) {
                if (board[i] < 0) {
                    if (isLegalMove((byte) i, (byte) (i + dice[0]))) {
                        moves.add(new byte[]{(byte) i, (byte) (i + dice[0])});
                    }
                    if (isLegalMove((byte) i, (byte) (i + dice[1]))) {
                        moves.add(new byte[]{(byte) i, (byte) (i + dice[1])});
                    }
                    if (board[0] != 0) // in the bar
                    {
                        return;
                    }
                }
            } else {
                if (board[25 - i] > 0) {
                    if (isLegalMove((byte) (25 - i), (byte) (25 - i - dice[0]))) {
                        moves.add(new byte[]{(byte) (25 - i), (byte) (25 - i - dice[0])});
                    }
                    if (isLegalMove((byte) (25 - i), (byte) (25 - i - dice[1]))) {
                        moves.add(new byte[]{(byte) (25 - i), (byte) (25 - i - dice[1])});
                    }
                    if (board[25] != 0) // in the bar
                    {
                        return;
                    }
                }
            }
        }
    }

    /*
     * Change state of the Player
     */
    public void changePlayer() {
        whosMove = !whosMove;
    }
    
    /*
     * Check of the user is won or not
     */
    public boolean checkWinner(){
    	if (whosMove){
    		if (board[27] == -15)
    			return true;
    	} else 
    		if (board[26]== 15)
    			return true;
    	return false;
    }
    
    /*
     * return winner otherwise null
     */
    public boolean getWinner(){
    	if (board[27] == -15)
			return true;
    	else
			return false;
    }

    /*
     * Show possible positions
     */
    public void showPossiblePositions() {
        //GetPossiblePositions();
    	//ArrayList<byte[]> moves = new ArrayList<byte[]>(this.moves);
        for (int i = 0; i < moves.size(); i++) {
        	System.out.println("possible position: " + moves.get(i)[0] + " to " + moves.get(i)[1]);
        }
    }

    /**
     * prints cute text to Console with the avaliable dice throws
     */
    public void printDice() {
        System.out.print("Dice: ");
        for ( int i = 0; i < dice.length; i++ )
        if (dice[0] > 0) {
            System.out.print("[" + dice[i] + "] ");
        } 
    }

    protected void start() {
        System.out.println();
    }
    
    /*
     * Count numbers
     */
    public void PrintWhiteBlack(){
    	int b=0,w=0;
    	for (int i = 0; i < 26; i++) {
			if (board[i]<0)
				w+=board[i];
			else
				b+=board[i];				
		}
    	System.out.println("total B: "+b+ ", W: "+w);
    }
    /*
     * Local variables getters and setters
     */

    public Random getRandom() {
        return random;
    }

    public void setRandom(Random random) {
        this.random = random;
    }

    public void setPlayer2(String player2) {
        this.player_black = player2;
    }

    public byte[] getBoard() {
        return board;
    }

    public void setBoard(byte[] board) {
        this.board = board;
    }

    public void setWhosMoveItIs(boolean whosMoveItIs) {
        this.whosMove = whosMoveItIs;
    }

    public byte[] getDice() {
        return dice;
    }

    public void setDice(byte[] dice) {
        this.dice = dice;
    }

    public boolean isReadyWhite() {
        return readyWhite;
    }

    public void setReadyWhite(boolean readyWhite) {
        this.readyWhite = readyWhite;
    }

    public boolean isReadyBlack() {
        return readyBlack;
    }

    public void setReadyBlack(boolean readyBlack) {
        this.readyBlack = readyBlack;
    }

    public ArrayList<byte[]> getMoves() {
        return new ArrayList<byte[]>(moves);
    }

    public void setMoves(ArrayList<byte[]> moves) {
        this.moves = moves;
    }

    /*
     * Print board as array
     */
    public void printBoardArray(){
        String s = System.getProperty("line.separator");

    	for (int i : board) { s += i + ", "; }
        
        System.out.print(s.substring(0, s.length()-2));
    }
    /*
     * copy of the Baord
     *
     */
    public Object clone() {
        try {
        	BGBoard bg = (BGBoard)super.clone();
        	bg.setMoves((ArrayList<byte[]>)moves.clone());
        	bg.setBoard(board.clone());
        	bg.setDice(dice.clone());
            return bg;
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            System.out.println("Doesn't support for clone!");
        }
        return null;
    }

    public static String dieString(int value) {
        String newline = System.getProperty("line.separator");
        switch (value) {
            case 1: return newline +
                           "\t\t  ______" + newline +
                           "\t\t /____ /|" + newline +
                           "\t\t|     | |" + newline +
                           "\t\t|  ¤  | |" + newline +
                           "\t\t|_____|/" + newline;

            case 2: return newline +
                           "\t\t  ______" + newline +
                           "\t\t /____ /|" + newline +
                           "\t\t|     | |" + newline +
                           "\t\t| ¤ ¤ | |" + newline +
                           "\t\t|_____|/" + newline;

            case 3: return newline +
                           "\t\t  ______" + newline +
                           "\t\t /____ /|" + newline +
                           "\t\t|¤    | |" + newline +
                           "\t\t|  ¤  | |" + newline +
                           "\t\t|____¤|/" + newline;

            case 4: return newline +
                           "\t\t  ______" + newline +
                           "\t\t /____ /|" + newline +
                           "\t\t| ¤ ¤ | |" + newline +
                           "\t\t|     | |" + newline +
                           "\t\t|_¤_¤_|/" + newline;

            case 5: return newline +
                           "\t\t  ______" + newline +
                           "\t\t /____ /|" + newline +
                           "\t\t| ¤ ¤ | |" + newline +
                           "\t\t|  ¤  | |" + newline +
                           "\t\t|_¤_¤_|/" + newline;

            case 6: return newline +
                           "\t\t  ______" + newline +
                           "\t\t /____ /|" + newline +
                           "\t\t| ¤ ¤ | |" + newline +
                           "\t\t| ¤ ¤ | |" + newline +
                           "\t\t|_¤_¤_|/" + newline;
            
            default: return newline + "No dice." + newline;
        }
    }

    public void setPlayer_black(String player_black) {
        this.player_black = player_black;
    }

    public void setPlayer_white(String player_white) {
        this.player_white = player_white;
    }

    
}
